/*
 * ENUM Discoverer. ENUM Demonstration software, demonstrating the potential of ENUM.
 * 
 * Copyright (C) 2009 SIDN and ISOC.nl
 * 
 * This file is part of ENUM Discoverer.
 *
 * ENUM Discoverer is free software: you can redistribute it and/or modify
 * it under the terms of the GNU Affero General Public License as published by
 * the Free Software Foundation, either version 3 of the License, or
 * (at your option) any later version.
 *
 * ENUM Discoverer is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 * GNU Affero General Public License for more details.
 *
 * You should have received a copy of the GNU Affero General Public License
 * along with ENUM Discoverer.  If not, see <http://www.gnu.org/licenses/>.
 */

package nl.sidn.enumdiscoverer.contacts.dao;

import java.io.ByteArrayInputStream;
import java.io.ByteArrayOutputStream;
import java.io.IOException;
import java.io.InputStream;
import java.io.ObjectInputStream;
import java.io.ObjectOutputStream;
import java.util.Date;
import java.util.HashMap;
import java.util.regex.Pattern;

import org.apache.commons.codec.binary.Base64;

import nl.sidn.enumdiscoverer.AppConstants;
import nl.sidn.enumdiscoverer.ExtendedContactData;
import nl.sidn.enumdiscoverer.LogConfig;
import nl.sidn.enumdiscoverer.Utils;
import nl.sidn.enumdiscoverer.contacts.ContactDetails;
import android.content.ContentResolver;
import android.content.ContentUris;
import android.content.ContentValues;
import android.content.Context;
import android.database.Cursor;
import android.net.Uri;
import android.provider.Contacts;
import android.provider.Contacts.ContactMethods;
import android.provider.Contacts.Extensions;
import android.provider.Contacts.Organizations;
import android.provider.Contacts.People;
import android.provider.Contacts.Phones;
import android.util.Log;

/**
 * Handles the writing of contact data associated with a particular person to the
 * Android 1.6 contacts API
 * @author	Grant Patterson (grant.patterson@catalyst.net.nz)
 */
public class Android16PersonDao extends PersonDao {

	static final String TAG = LogConfig.getLogTag(Android16PersonDao.class);
	// whether DEBUG level logging is enabled (whether globally, or explicitly for this log tag)
	static final boolean DEBUG = LogConfig.isDebug(TAG);
	// whether VERBOSE level logging is enabled
	static final boolean VERBOSE = LogConfig.VERBOSE;
	
	private static final String EXTENSIONS_KEY_SUFFIX = "otherDetails";
	
	private final long mPersonId;
	private final String mExtensionsKey;
	private Context mContext;
	
	// details we can't store directly in Contacts gets collected here and
	// serialised to Contacts.Extensions
	private ExtendedContactData mExtendedData;
	
	
	Android16PersonDao(Context context, long personId, boolean readExtrasFromDb) {
		super();
		
		if (VERBOSE) Log.v(TAG, this + " created");
		
		String key = context.getPackageName() + "." + EXTENSIONS_KEY_SUFFIX;
		
		mPersonId = personId;
		mExtensionsKey = key;
		mContext = context;
		
		if (readExtrasFromDb) {
			Object obj = deserialiseFromExtension(key);
			if (obj != null && obj instanceof ExtendedContactData) {
				mExtendedData = (ExtendedContactData)obj;
				return;
			} // else something went wrong - just create a new one
		}
		
		mExtendedData = new ExtendedContactData();
	}
	
	@Override
	public HashMap<Integer, String> getWebLinks() {
		return mExtendedData.getWebLinks();
	}
	
	@Override
	public void setWebLinks(HashMap<Integer, String> links) {
		mExtendedData.setWebLinks(links);
	}
	
	@Override
	public long getId() {
		return mPersonId;
	}
	
	@Override
	public ContactDetails getDetails() {
		
		ContactDetails details = new ContactDetails();
		long orgId = -1;
		
		ContentResolver resolver = mContext.getContentResolver();
		
		Uri uri = ContentUris.withAppendedId(People.CONTENT_URI, mPersonId);
		String[] projection = {People.NAME, People.PRIMARY_ORGANIZATION_ID};
		
		Cursor c = resolver.query(uri, projection, null, null, null);
		try {
			if (c.getCount() == 0) {
				Log.w(TAG, "Can't get details. Person " + mPersonId + " doesn't exist");
				return details;
			}
			
			c.moveToNext();
			details.setName(c.getString(c.getColumnIndex(People.NAME)));
			orgId = c.getLong(c.getColumnIndex(People.PRIMARY_ORGANIZATION_ID));
			
		} finally {
			c.close();
		}
		
		details.setPostalAddress(getPrimaryPostalAddress());
		
		
		uri = ContentUris.withAppendedId(Organizations.CONTENT_URI, orgId);
		
		projection = new String[]{Organizations.COMPANY};
		
		c = resolver.query(uri, projection, null, null, null);
		try {
			if (c.getCount() > 0) {
				c.moveToNext();
				details.setOrganisation(c.getString(c.getColumnIndex(Organizations.COMPANY)));
			}
		} finally {
			c.close();
		}
		
		return details;
	}
	
	@Override
	public void addPhoneNumber(String number) {
		
		long personId = mPersonId;  // copy to stack
		ContentResolver resolver = mContext.getContentResolver();
		
		// if a phone number matching enumData already exists for the Contact, then do nothing.
		// Otherwise, insert the new phone number
		Uri contentUri = Phones.CONTENT_URI;
		String selection = Phones.PERSON_ID + "=?" + " AND " + Phones.NUMBER + "=?";
		String[] projection = {Phones._ID};
		String[] selectionArgs = {Long.toString(personId), number};
		Cursor c = resolver.query(contentUri, projection, selection, selectionArgs, null);
		try {
			if (c.getCount() != 0) {
				if (VERBOSE) Log.v(TAG, "contact " + personId + " already has phone number " + number);
				return;
			}
		} finally {
			c.close();
		}
		
		ContentValues values = new ContentValues();
		values.put(Phones.PERSON_ID, personId);
		values.put(Phones.TYPE, Phones.TYPE_OTHER);
		values.put(Phones.NUMBER, number);
		
		Uri uri = resolver.insert(contentUri, values);
		if (VERBOSE && uri != null) {
			Log.v(TAG, "Added phone number " + number + " to contact " + personId + ": " + uri);
		} else {
			Log.w(TAG, "Failed to write phone number " + number + " to contact " + personId);
		}
	}
	
	@Override
	public void addPostalAddress(String address) {
		
		address = address.trim();
		
		ContentResolver resolver = mContext.getContentResolver();
		long personId = mPersonId;  // copy to stack
		
		// if a postal address matching address already exists for the Contact, then do nothing.
		// Otherwise, insert the new postal address
		
		Uri uri = ContactMethods.CONTENT_URI;
		String[] projection = {ContactMethods._ID, ContactMethods.DATA};
		String selection = ContactMethods.PERSON_ID + "=?" +
				" AND " + ContactMethods.KIND + "=?";
		String[] selectionArgs = {Long.toString(personId), Integer.toString(Contacts.KIND_POSTAL)};
		
		Cursor c = resolver.query(uri, projection, selection, selectionArgs, null);
		
		try {
			
			int addressCol = c.getColumnIndex(ContactMethods.DATA);
			
			while (c.moveToNext()) {
				if (compareAddresses(address, c.getString(addressCol))) {
					if (VERBOSE) Log.v(TAG, "contact " + personId + " already has postal address '" + address + "'");
					return;
				}
			}
			
		} finally {
			c.close();
		}
		
		// insert the new address
		
		ContentValues values = new ContentValues();
		values.put(ContactMethods.KIND, Contacts.KIND_POSTAL);
		values.put(ContactMethods.PERSON_ID, personId);
		values.put(ContactMethods.TYPE, ContactMethods.TYPE_OTHER);
		values.put(ContactMethods.DATA, address);
		
		Uri rowUri = resolver.insert(ContactMethods.CONTENT_URI, values);
		if (VERBOSE && rowUri != null) {
			Log.v(TAG, "Added postal address '" + address + "' to contact " + personId + ": " + rowUri);
		} else if (rowUri == null) {
			Log.w(TAG, "Failed to write postal address '" + address + "' to contact " + personId);
		}
	}
	
	/**
	 * Compares two addresses and determines whether they are similar enough to be considered duplicates
	 * @param address1
	 * @param address2
	 * @return true if the addresses are similar enough to be considered duplicates
	 */
	private boolean compareAddresses(String address1, String address2) {
		
		// split the addresses on whitespace and compare the words for (case independent) equality
		
		Pattern splitRegex = Pattern.compile("\\w");
		String[] a1Words = splitRegex.split(address1.trim());
		String[] a2Words = splitRegex.split(address2.trim());
		
		if (a1Words.length != a2Words.length) {
			return false;
		}
		
		int len = a1Words.length;
		for (int i = 0; i < len; i++) {
			if (!a1Words[i].equalsIgnoreCase(a2Words[i])) {
				return false;
			}
		}
		
		// all words are the same
		
		return true;
	}
	
	@Override
	public String getPrimaryPostalAddress() {
		
		Uri uri = ContentUris.withAppendedId(People.CONTENT_URI, mPersonId);
		uri = Uri.withAppendedPath(uri, People.ContactMethods.CONTENT_DIRECTORY);
		
		String[] projection = {ContactMethods.NAME, ContactMethods.DATA};
		String selection;
		String[] selectionArgs;
		
		// pre-eclair we can get the primary postal address as desired
		if (AppConstants.SDK_VERSION < 5) {
			
			selection = ContactMethods.KIND + "=?" + " AND " + ContactMethods.ISPRIMARY + "=?";
			selectionArgs = new String[]{Integer.toString(Contacts.KIND_POSTAL), Integer.toString(1)};
		
		// Eclair can't find a primary postal address, so ask for all postal address and return the first one
		} else {
			
			selection = ContactMethods.KIND + "=?";
			selectionArgs = new String[]{Integer.toString(Contacts.KIND_POSTAL)};
		}
		
		if (VERBOSE) Log.v(TAG, "Querying Contacts: " + uri);
		
		Cursor c = mContext.getContentResolver().query(uri, projection, selection, selectionArgs, null);
		try {
			if (c.getCount() == 0) {
				Log.w(TAG, "No address found for personId = " + mPersonId);
				return null;
			}
			
			c.moveToNext();
			return c.getString(c.getColumnIndex(ContactMethods.DATA));
			
		} finally {
			c.close();
		}
	}
	
	@Override
	public void addOrganisation(String org) {
		
		ContentResolver resolver = mContext.getContentResolver();
		long personId = mPersonId;  // copy to stack
		
		// if an organisation matching org already exists for the Contact, then do nothing.
		// Otherwise, insert the new organisation
		
		Uri uri = Organizations.CONTENT_URI;
		String[] projection = {Organizations._ID};
		String selection = Organizations.PERSON_ID + "=?" + " AND " + Organizations.COMPANY + "=?";
		String[] selectionArgs = {Long.toString(personId), org};
		Cursor c = resolver.query(uri, projection, selection, selectionArgs, null);
		try {
			if (c.getCount() != 0) {
				if (VERBOSE) Log.v(TAG, "contact " + personId + " already has organisation '" + org + "'");
				return;
			}
		} finally {
			c.close();
		}
		
		// insert the new address
		
		ContentValues values = new ContentValues();
		values.put(Organizations.PERSON_ID, personId);
		values.put(Organizations.COMPANY, org);
		values.put(Organizations.TYPE, Organizations.TYPE_OTHER);
		
		Uri rowUri = resolver.insert(Organizations.CONTENT_URI, values);
		if (VERBOSE && rowUri != null) {
			Log.v(TAG, "Added organisation '" + org + "' to contact " + personId + ": " + rowUri);
		} else if (rowUri == null) {
			Log.w(TAG, "Failed to write organisation '" + org + "' to contact " + personId);
		}
	}
	
	@Override
	public InputStream getPhoto() {
		
		Uri personUri = ContentUris.withAppendedId(People.CONTENT_URI, mPersonId);
		
		return People.openContactPhotoInputStream(mContext.getContentResolver(), personUri);
	}
	
	@Override
	public void setPhoto(byte[] photoBytes) {
		
		// Note: there doesn't appear to be any way to stream to a Contact image.
		// ContentResolver.openOutputStream() doesn't have permission to write the file.
		
		Uri personUri = ContentUris.withAppendedId(People.CONTENT_URI, mPersonId);
		People.setPhotoData(mContext.getContentResolver(), personUri, photoBytes);
	}
	
	@Override
	public void addEmailAddress(String address) {
		
		ContentResolver resolver = mContext.getContentResolver();
		long personId = mPersonId;  // copy to stack
		
		// if an email address matching address already exists for the Contact, then do nothing.
		// Otherwise, insert the new email address
		
		Uri uri = ContactMethods.CONTENT_EMAIL_URI;
		String[] projection = {ContactMethods._ID};
		String selection = ContactMethods.PERSON_ID + "=?" + " AND " + ContactMethods.DATA + "=?";
		String[] selectionArgs = {Long.toString(personId), address};
		Cursor c = resolver.query(uri, projection, selection, selectionArgs, null);
		try {
			if (c.getCount() != 0) {
				if (VERBOSE) Log.v(TAG, "contact " + personId + " already has email address " + address);
				return;
			}
		} finally {
			c.close();
		}
		
		// Note: ContactMethods.CONTENT_EMAIL_URI doesn't work for inserts.
		// Need to use ContactMethods.CONTENT_URI and (ContactMethods.KIND = Contacts.KIND_EMAIL).
		
		ContentValues values = new ContentValues();
		values.put(ContactMethods.KIND, Contacts.KIND_EMAIL);
		values.put(ContactMethods.PERSON_ID, personId);
		values.put(ContactMethods.TYPE, ContactMethods.TYPE_OTHER);
		values.put(ContactMethods.DATA, address);
		
		Uri rowUri = resolver.insert(ContactMethods.CONTENT_URI, values);
		if (VERBOSE && rowUri != null) {
			Log.v(TAG, "Added email " + address + " to contact " + personId + ": " + rowUri);
		} else if (rowUri == null) {
			Log.w(TAG, "Failed to write email " + address + " to contact " + personId);
		}
	}
	
	@Override
	public void addInstantMessagingUrl(String imUrl, int protocol) {
		
		ContentResolver resolver = mContext.getContentResolver();
		long personId = mPersonId;  // copy to stack
		
		// if an IM URL matching url already exists for the Contact, then do nothing.
		// Otherwise, insert the IM URL
		
		Uri queryUri = ContactMethods.CONTENT_URI;
		String selection = ContactMethods.PERSON_ID + "=? AND " + ContactMethods.KIND + "=" +
				Contacts.KIND_IM + " AND " + ContactMethods.DATA + "=?";
		String[] projection = { ContactMethods._ID };
		String[] selectionArgs = { Long.toString(personId), imUrl };
		Cursor c = resolver.query(queryUri, projection, selection, selectionArgs, null);
		try {
			if (c.getCount() != 0) {
				if (VERBOSE) Log.v(TAG, "contact " + personId + " already has instant messaging URL " + imUrl);
				return;
			}
		} finally {
			c.close();
		}
		
		// determine the Contacts protocol string to use
		String protocolString;
		switch (protocol) {
		case ImProtocols.AIM:
			protocolString = ContactMethods.encodePredefinedImProtocol(ContactMethods.PROTOCOL_AIM);
			break;
		case ImProtocols.GTALK:
			protocolString = ContactMethods.encodePredefinedImProtocol(ContactMethods.PROTOCOL_GOOGLE_TALK);
			break;
		case ImProtocols.ICQ:
			protocolString = ContactMethods.encodePredefinedImProtocol(ContactMethods.PROTOCOL_ICQ);
			break;
		case ImProtocols.JABBER:
			protocolString = ContactMethods.encodePredefinedImProtocol(ContactMethods.PROTOCOL_JABBER);
			break;
		case ImProtocols.MSN:
			protocolString = ContactMethods.encodePredefinedImProtocol(ContactMethods.PROTOCOL_MSN);
			break;
		case ImProtocols.QQ:
			protocolString = ContactMethods.encodePredefinedImProtocol(ContactMethods.PROTOCOL_QQ);
			break;
		case ImProtocols.SKYPE:
			protocolString = ContactMethods.encodePredefinedImProtocol(ContactMethods.PROTOCOL_SKYPE);
			break;
		case ImProtocols.YAHOO:
			protocolString = ContactMethods.encodePredefinedImProtocol(ContactMethods.PROTOCOL_YAHOO);
			break;
		default:
			Log.i(TAG, "Invalid instant messaging protocol code: " + protocol + ". " +
					imUrl + " won't be stored to Contacts");
			return;
		}

		// construct the query
		ContentValues values = new ContentValues();
		values.put(Contacts.ContactMethods.KIND, Contacts.KIND_IM);
		values.put(ContactMethods.PERSON_ID, personId);
		values.put(ContactMethods.TYPE, ContactMethods.TYPE_OTHER);
		values.put(ContactMethods.DATA, imUrl);
		values.put(ContactMethods.AUX_DATA, protocolString);

		Uri rowUri = resolver.insert(queryUri, values);
		
		if (VERBOSE && rowUri != null) {
			Log.v(TAG, "Added IM " + imUrl + " to contact " + personId + ": " + rowUri);
		} else if (rowUri == null) {
			Log.i(TAG, "Failed to write IM " + imUrl + " to contact " + personId);
		}
	}
	
	@Override
	public void setLocation(Coords location) {
		
		mExtendedData.setLocation(location);
	}
	
	@Override
	public Coords getLocation() {
		return mExtendedData.getLocation();
	}
	
	@Override
	public void setName(String name) {
		
		// overwrite existing name
		
		ContentResolver resolver = mContext.getContentResolver();
		
		Uri uri = ContentUris.withAppendedId(People.CONTENT_URI, mPersonId);
		
		ContentValues values = new ContentValues();
		values.put(People.NAME, name);
		
		// Android 2.0 has a bug where it will throw an exception if there is no default
		// account unless one of CUSTOM_RINGTONE, SEND_TO_VOICEMAIL, LAST_TIME_CONTACTED,
		// TIMES_CONTACTED, or STARRED are set in values. We include one to avoid this
		if (AppConstants.SDK_VERSION >= 5) {
			
			// get current value of People.STARRED
			
			int starred;
			
			String[] projection = {People.STARRED};
			Cursor cursor = resolver.query(uri, projection, null, null, null);
			
			try {
				cursor.moveToFirst();
				starred = cursor.getInt(cursor.getColumnIndex(People.STARRED));
			} finally {
				cursor.close();
			}
			
			// include current value of STARRED in values
			values.put(People.STARRED, starred);
		}
		
		int updates = resolver.update(uri, values, null, null);
		if (VERBOSE && updates > 0) {
			Log.v(TAG, "Updated contact " + mPersonId + " name to " + name);
		}
	}
	
	@Override
	public void close() {
		
		if (VERBOSE) Log.v(TAG, this + " closed");
		
		serialiseToExtension(mExtensionsKey, mExtendedData);
		
		// clear reference to context so we don't cause a memory leak
		mContext = null;
	}
	
	/**
	 * Serialise obj and put it in the Extensions table in Contacts under name.
	 * @param name The name to store the object under
	 * @param obj The object to store in Extensions
	 */
	private void serialiseToExtension(String name, Object obj) {
		
		byte[] encodedBytes;
		
		// serialise the object and Base64 encode into encodedBytes
		ObjectOutputStream oos = null;
		try {
			
			ByteArrayOutputStream byteStream = new ByteArrayOutputStream();
			
			oos = new ObjectOutputStream(byteStream);
			oos.writeObject(obj);
			
			Base64 enc = new Base64();
			encodedBytes = enc.encode(byteStream.toByteArray());
			
		} catch (IOException e) {
			Log.w(TAG, "Failed to serialise " + obj.getClass() +
					" object to Contacts.Extensions key " + name, e);
			return;
			
		} finally {
			Utils.close(oos);
		}
		
		String objStr = new String(encodedBytes);
		
		// write the encoded object string to Contacts.Extensions
		
		ContentResolver resolver = mContext.getContentResolver();
		
		if (containsExtension(name, resolver)) {
			updateExtension(name, objStr, resolver);
		} else {
			insertExtension(name, objStr, resolver);
		}
	}
	
	private boolean containsExtension(String name, ContentResolver resolver) {
		
		Uri uri = ContentUris.withAppendedId(People.CONTENT_URI, mPersonId);
		uri = Uri.withAppendedPath(uri, People.Extensions.CONTENT_DIRECTORY);
		
		String[] projection = {Extensions.PERSON_ID};
		
		String selection = Extensions.NAME + "='" + name + "'";
		
		Cursor c = resolver.query(uri, projection, selection, null, null);
		
		try {
			
			return c.getCount() == 1;
			
		} finally {
			c.close();
		}
	}
	
	private void insertExtension(String name, String value, ContentResolver resolver) {
		
		// NOTE: this URI doesn't work for inserts in the Android 2.0 Contacts legacy support.
		// We need to use Extensions.CONTENT_URI and include the person_id in the ContentValues
		//Uri uri = buildPersonExtensionUri(mPersonId);
		
		Uri uri = Extensions.CONTENT_URI;
		
		ContentValues values = new ContentValues();
		values.put(Extensions.PERSON_ID, mPersonId);
		values.put(Contacts.Extensions.NAME, name);
		values.put(Contacts.Extensions.VALUE, value);
		
		uri = resolver.insert(uri, values);
		Log.i(TAG, "Inserted a new extension: " + uri + " for personId=" + mPersonId);
	}
	
	// precondition: the extension must already exist
	private void updateExtension(String name, String value, ContentResolver resolver) {
		
		// ContentResolver.update() only supports Extension URLs that contain the extension_id (in SDK 1.6),
		// so we are forced to do a lookup first.
		long extId;
		Uri uri = buildPersonExtensionUri(mPersonId);
		String[] projection = {Extensions._ID};
		String selection = Extensions.NAME + "='" + name + "'";
		Cursor c = resolver.query(uri, projection, selection, null, null);
		try {
			c.moveToNext();
			extId = c.getLong(0);
		} finally {
			c.close();
		}
		
		// Now we can build a URI with the extension_id
		uri = ContentUris.withAppendedId(Extensions.CONTENT_URI, extId);
		
		ContentValues values = new ContentValues();
		values.put(Contacts.Extensions.VALUE, value);
		
		String where = Extensions.NAME + "='" + name + "'";
		
		int num = resolver.update(uri, values, where, null);
		if (DEBUG) Log.d(TAG, "Updated " + num + " extension(s) at " + uri);
	}
	
	private static Uri buildPersonExtensionUri(long personId) {
		Uri uri = ContentUris.withAppendedId(People.CONTENT_URI, personId);
		return Uri.withAppendedPath(uri, People.Extensions.CONTENT_DIRECTORY);
	}
	
	private Object deserialiseFromExtension(String name) {
		
		String objStr = getExtension(mExtensionsKey);
		
		if (objStr == null) {
			return null;
		}
		
		// decode from Base64
		Base64 dec = new Base64();
		byte[] bytes = dec.decode(objStr.getBytes());
		
		// deserialise
		ByteArrayInputStream bis = new ByteArrayInputStream(bytes);
		ObjectInputStream ois = null;
		try {
			
			ois = new ObjectInputStream(bis);
			
			return ois.readObject();
			
		} catch (IOException e) {
			Log.w(TAG, "Failed to deserialise Contacts.Extension from key " + name, e);
			return null;
		} catch (ClassNotFoundException e) {
			Log.w(TAG, "Failed to deserialise Contacts.Extension from key " + name, e);
			return null;
			
		} finally {
			Utils.close(ois);
		}
	}
	
	private String getExtension(String name) {
		
		Uri uri = buildPersonExtensionUri(mPersonId);
		
		String[] projection = {Extensions.VALUE};
		
		String selection = Extensions.NAME + "='" + name + "'";
		
		Cursor c = mContext.getContentResolver().query(uri, projection, selection, null, null);
		try {
			
			if (c.getCount() == 1) {
				
				c.moveToNext();
				return c.getString(c.getColumnIndex(Extensions.VALUE));
				
			} else {
				
				if (DEBUG) Log.d(TAG, "No extension found for key=" + name + " id=" + mPersonId);
				return null;
			}
		} finally {
			c.close();
		}
	}

	@Override
	public String getPublicKey() {
		return mExtendedData.getPublicKey();
	}

	@Override
	public void setPublicKey(String key) {
		mExtendedData.setPublicKey(key);
	}
	
	@Override
	public String getSIPAddress() {
		return mExtendedData.getSIPAddress();
	}
	
	@Override
	public void setSipAddress(String address) {
		mExtendedData.setSIPAddress(address);
	}
	
	@Override
	public Date getDateOfBirth() {
		return mExtendedData.getBirthday();
	}

	@Override
	public void setDateOfBirth(Date birthday) {
		mExtendedData.setDateOfBirth(birthday);
	}

	@Override
	public void setStatus(String status) {
		mExtendedData.setStatus(status);		
	}
	
	@Override
	public String getStatus() {
		return mExtendedData.getStatus();		
	}
}
